package online.model;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import core.config.Factory;
import core.util.DateUtil;

/**
 * 汎用モデルクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class UniModelImpl implements UniModel {
	/** serialVersionUID */
	private static final long serialVersionUID = 2213460856379086228L;

	/** データ保持マップ */
	private final UniMap<String, Serializable> um = new UniMap<>();
	/** リザーブ用マップ */
	private final Set<String> pm;

	/** 現在日時 */
	private Timestamp dateTime = null;

	/**
	 * コンストラクタ
	 *
	 */
	public UniModelImpl() {
		this.pm = null;
	}

	/**
	 * コンストラクタ
	 *
	 * @param val リザーブ可フラグ
	 */
	public UniModelImpl(final boolean val) {
		this(null, val);
	}

	/**
	 * コンストラクタ
	 *
	 * @param model 汎用モデル
	 */
	public UniModelImpl(final UniModel model) {
		this(model, model.isReservable());
	}

	/**
	 * コンストラクタ
	 *
	 * @param model 汎用モデル
	 * @param val リザーブ可フラグ
	 */
	public UniModelImpl(final UniModel model, final boolean val) {
		if (val) {
			this.pm = new HashSet<>();
		} else {
			this.pm = null;
		}

		if (model != null) {
			putAll(model);
			setDateTime(model.getDateTime());

			if (this.pm != null) {
				this.pm.addAll(model.getReservedKeySet());
			}
		}
	}

	/**
	 * 日時取得
	 *
	 * @return 日時
	 */
	@Override
	public Timestamp getDateTime() {
		return DateUtil.copyOf(this.dateTime);
	}

	/**
	 * 日時設定
	 *
	 * @param val 日時
	 */
	@Override
	public void setDateTime(final Timestamp val) {
		if (this.dateTime == null && val != null) {
			this.dateTime = DateUtil.copyOf(val);
		}
	}

	/**
	 * 保存可状態取得
	 *
	 * @return 保存可能の場合 true を返す。
	 */
	@Override
	public boolean isReservable() {
		return this.pm != null;
	}

	/**
	 * キー値保存
	 *
	 * @param val キー
	 */
	@Override
	public void reserve(final String... val) {
		if (this.pm == null) {
			throw new IllegalStateException("can not reserve.");
		}

		for (final String key : val) {
			if (this.um.containsKey(key)) {
				this.pm.add(key);
			}
		}
	}

	/**
	 * 保存モデル取得
	 *
	 * @return 保存モデル
	 */
	@Override
	public Set<String> getReservedKeySet() {
		if (this.pm == null) {
			return Collections.emptySet();
		}

		return this.pm;
	}

	/**
	 * 値保持マップ取得
	 *
	 * @return Mapオブジェクト
	 */
	@Override
	public Map<String, Serializable> toMap() {
		return Collections.unmodifiableMap(this.um);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Boolean value) {
		setValue(key, value, Boolean.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Boolean... value) {
		setValue(key, value, Boolean[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final String value) {
		setValue(key, value, String.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final String... value) {
		setValue(key, value, String[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Byte value) {
		setValue(key, value, Byte.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Byte... value) {
		checkNumberArray(value);
		setValue(key, value, Byte[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Short value) {
		setValue(key, value, Short.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Short... value) {
		checkNumberArray(value);
		setValue(key, value, Short[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Integer value) {
		setValue(key, value, Integer.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Integer... value) {
		checkNumberArray(value);
		setValue(key, value, Integer[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Long value) {
		setValue(key, value, Long.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Long... value) {
		checkNumberArray(value);
		setValue(key, value, Long[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Float value) {
		setValue(key, value, Float.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Float... value) {
		checkNumberArray(value);
		setValue(key, value, Float[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Double value) {
		setValue(key, value, Double.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Double... value) {
		checkNumberArray(value);
		setValue(key, value, Double[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final BigInteger value) {
		setValue(key, value, BigInteger.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final BigInteger... value) {
		checkNumberArray(value);
		setValue(key, value, BigInteger[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final BigDecimal value) {
		setValue(key, value, BigDecimal.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final BigDecimal... value) {
		checkNumberArray(value);
		setValue(key, value, BigDecimal[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Number value) {
		if (value == null) {
			setValue(key, null, BigDecimal.class);
		} else {
			setValue(key, value, value.getClass());
		}
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Number... value) {
		if (value == null) {
			setValue(key, null, BigDecimal[].class);
		} else {
			checkNumberArray(value);
			setValue(key, value, value.getClass());
		}
	}

	/**
	 * Number配列確認
	 * @param obj 値
	 */
	private void checkNumberArray(final Number[] obj) {
		if (obj != null && Number[].class.equals(obj.getClass())) {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Date value) {
		setValue(key, value, Date.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final Date... value) {
		setValue(key, value, Date[].class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void setValue(final String key, final UniModel value) {
		setValue(key, value, UniModel.class);
	}

	/**
	 * キーに対応した値を保持する。
	 *
	 * @param key キー
	 * @param value 値
	 * @param cls クラス
	 */
	private void setValue(final String key, final Serializable value, final Class<?> cls) {
		if (value == null) {
			if (cls.isArray()) {
				Object obj = Array.newInstance(cls.getComponentType(), 0);
				this.um.put(key, Serializable[].class.cast(obj));
			} else {
				Object obj = Array.newInstance(cls, 0);
				this.um.putRaw(key, Serializable[].class.cast(obj));
			}
		} else {
			this.um.put(key, value);
		}
	}

	/**
	 * キーに対応した値を取得する。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Boolean getBoolean(final String key) {
		// 値取得
		Boolean ret = null;
		Object obj = this.um.get(key);
		if (Boolean.class.isInstance(obj)) {
			ret = Boolean.class.cast(obj);
		} else if (Boolean[].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				ret = Boolean[].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
					key + " is " + obj.getClass().getComponentType());
		}
		return ret;
	}

	/**
	 * キーに対応した値を取得する。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Boolean[] getBooleanArray(final String key) {
		// 値取得
		Object obj = this.um.getRaw(key);
		if (Boolean[].class.isInstance(obj)) {
			return Boolean[].class.cast(obj);
		} else if (Boolean[][].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return Boolean[][].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return new Boolean[0];
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーに対応した値がnullの場合、空文字列を返す。
	 * 指定したキーの値がモデルの場合、空文字列を返す。
	 * 指定したキーの値が配列の場合、添字0の値を返す。添字0が存在しない場合、空文字列を返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public String getString(final String key) {
		// 値取得
		Object obj = this.um.get(key);
		if (obj != null && !UniModel.class.isInstance(obj)) {
			// 配列
			if (obj.getClass().isArray()) {
				if (0 < Array.getLength(obj)) {
					obj = Object[].class.cast(obj)[0];
				} else {
					obj = null;
				}
			}
		}
		return Objects.toString(obj, "");
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーに対応した値がnullの場合、空配列を返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public String[] getStringArray(final String key) {
		// 値取得
		Object obj = this.um.getRaw(key);
		if (String[].class.isInstance(obj)) {
			return String[].class.cast(obj);
		} else if (String[][].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return String[][].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return new String[0];
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーの値が配列の場合、添字0の値を返す。添字0が存在しない場合、nullを返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Number getNumber(final String key) {
		// 値取得
		Object obj = this.um.get(key);
		if (Number.class.isInstance(obj)) {
			return Number.class.cast(obj);
		} else if (Number[].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return Number[].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return null;
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーに対応した値がnullの場合、空配列を返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Number[] getNumberArray(final String key) {
		// 値取得
		Object obj = this.um.getRaw(key);
		if (Number[].class.isInstance(obj)) {
			return Number[].class.cast(obj);
		} else if (Number[][].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return Number[][].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return new Number[0];
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーの値が配列の場合、添字0の値を返す。添字0が存在しない場合、nullを返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Date getDate(final String key) {
		// 値取得
		Object obj = this.um.get(key);
		if (Date.class.isInstance(obj)) {
			return Date.class.cast(obj);
		} else if (Date[].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return Date[].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return null;
	}

	/**
	 * キーに対応した値を取得する。
	 * 指定したキーに対応した値がnullの場合、空配列を返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public Date[] getDateArray(final String key) {
		// 値取得
		Object obj = this.um.getRaw(key);
		if (Date[].class.isInstance(obj)) {
			return Date[].class.cast(obj);
		} else if (Date[][].class.isInstance(obj)) {
			if (0 < Array.getLength(obj)) {
				return Date[][].class.cast(obj)[0];
			}
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return new Date[0];
	}

	/**
	 * キーに対応した値を取得する。
	 *
	 * @param key キー
	 * @return 値
	 */
	@Override
	public UniModel getModel(final String key) {
		Object obj = this.um.get(key);
		if (UniModel.class.isInstance(obj)) {
			return UniModel.class.cast(obj);
		} else if (obj != null) {
			throw new ClassCastException(
				key + " is " + obj.getClass().getComponentType());
		}

		return null;
	}

	/**
	 * キーに対応した値を他modelに複製設定する。
	 *
	 * @param model 汎用モデル
	 * @param key キー
	 */
	@Override
	public void copyValueTo(final UniModel model, final String... key) {
		if (key != null && model != null) {
			for (final String val : key) {
				if (this.um.containsKey(val)) {
					putValue(getCopiedValue(getValue(val, this)), getValueClass(val), val, model);
				}
			}
		}
	}

	/**
	 * キーに対応した値を他modelに設定する。
	 *
	 * @param model 汎用モデル
	 * @param key キー
	 */
	@Override
	public void putValueTo(final UniModel model, final String... key) {
		if (key != null && model != null) {
			for (final String val : key) {
				if (this.um.containsKey(val)) {
					putValue(getValue(val, this), getValueClass(val), val, model);
				}
			}
		}
	}

	/**
	 * 他のモデルの内容を全て設定する。
	 *
	 * @param model モデル
	 */
	@Override
	public void putAll(final UniModel model) {
		if (model != null) {
			for (final String key : model.keySet()) {
				putValue(getValue(key, model), model.getValueClass(key), key, this);
			}
		}
	}

	/**
	 * キーに対する値を設定する。
	 * @param val 値
	 * @param cls 値のクラス
	 * @param key キー
	 * @param model モデル
	 */
	private void putValue(final Serializable val, final Class<?> cls,
			final String key, final UniModel model) {
		if (cls == null) {
			model.noValue(key);
		} else if (!cls.isArray() && val != null && val.getClass().isArray()) {
			model.remove(key);
			Method m = Factory.getMethod(
					model.getClass(), "addValue", String.class, normalize(cls));
			Factory.invoke(model, m, key, Array.get(val, 0));
		} else {
			Method m = Factory.getMethod(
					model.getClass(), "setValue", String.class, normalize(cls));
			Factory.invoke(model, m, key, val);
		}
	}

	/**
	 * クラス正規化
	 * @param cls クラス
	 * @return 正規化クラス
	 */
	private Class<?> normalize(final Class<?> cls) {
		if (Factory.isSubclassOf(Date[].class, cls)) {
			return Date[].class;
		} else if (Factory.isSubclassOf(Date.class, cls)) {
			return Date.class;
		}
		return cls;
	}

	/**
	 * キーに対する値を取得する。
	 *
	 * @param key キー
	 * @param model モデル
	 * @return 値
	 */
	private Serializable getValue(final String key, final UniModel model) {
		Class<?> cls = model.getValueClass(key);
		if (String[].class.equals(cls)) {
			return model.getStringArray(key);
		} else if (Boolean[].class.equals(cls)) {
			return model.getBooleanArray(key);
		} else if (Factory.isSubclassOf(Number[].class, cls)) {
			return model.getNumberArray(key);
		} else if (Factory.isSubclassOf(Date[].class, cls)) {
			return model.getDateArray(key);
		} else {
			if (model.hasValue(key)) {
				if (String.class.equals(cls)) {
					return model.getStringArray(key);
				} else if (Boolean.class.equals(cls)) {
					return model.getBooleanArray(key);
				} else if (Factory.isSubclassOf(Number.class, cls)) {
					return model.getNumberArray(key);
				} else if (Factory.isSubclassOf(Date.class, cls)) {
					return model.getDateArray(key);
				} else {
					return model.getModel(key);
				}
			}
		}
		return null;
	}

	/**
	 * コピー配列
	 *
	 * @param obj オブジェクト
	 * @return コピーされた配列オブジェクト
	 */
	private Serializable getCopiedValue(final Serializable obj) {
		if (UniModel.class.isInstance(obj)) {
			UniModel ret = new UniModelImpl();
			UniModel m = UniModel.class.cast(obj);
			for (final String key : m.keySet()) {
				m.copyValueTo(ret, key);
			}
			return ret;
		} else if (obj != null && obj.getClass().isArray()) {
			int dim = Array.getLength(obj);
			Object ret = Array.newInstance(obj.getClass().getComponentType(), dim);
			for (int i = 0; i < dim; i++) {
				Array.set(ret, i, getCopiedValue(Serializable.class.cast(Array.get(obj, i))));
			}
			return Serializable.class.cast(ret);
		}

		return obj;
	}

	/**
	 * 別名キーを作成する。
	 *
	 * @param key キー
	 * @param alias 別キー
	 */
	@Override
	public void aliasKey(final String key, final String alias) {
		if (!Objects.toString(key, "").trim().isEmpty()
				&& !Objects.toString(alias, "").trim().isEmpty()) {
			if (!key.equals(alias) && this.um.containsKey(key)) {
				this.um.putRaw(alias, this.um.getRaw(key));
			}
		}
	}

	/**
	 * キーが含まれているかを返す。
	 *
	 * @param key キー
	 * @return boolean
	 */
	@Override
	public boolean containsKey(final String key) {
		return this.um.containsKey(key);
	}

	/**
	 * キーに対応した値が配列かを返す。
	 *
	 * @param key キー
	 * @return 配列長
	 */
	@Override
	public int getArraySize(final String key) {
		return this.um.getArraySize(key);
	}

	/**
	 * キーに対応した値のクラスを返す。
	 *
	 * @param key キー
	 * @return クラス
	 */
	@Override
	public Class<?> getValueClass(final String key) {
		Class<?> cls =  this.um.getValueClass(key);
		if (cls != null) {
			if (Factory.isSubclassOf(UniModel.class, cls)) {
				return UniModel.class;
			}
		}
		return cls;
	}

	/**
	 * 空値確認
	 * @param key キー
	 * @return 値が存在して、長さ1以上の配列の場合 true を返す。
	 */
	@Override
	public boolean hasValue(final String key) {
		return this.um.containsKey(key) && 0 < this.um.getArraySize(key);
	}

	/**
	 * 空値設定
	 *
	 * @param key キー
	 */
	@Override
	public void noValue(final String... key) {
		if (key != null) {
			for (final String val : key) {
				this.um.putRaw(val, null);
			}
		}
	}

	/**
	 * キーに対応した値を削除する。
	 *
	 * @param key キー
	 */
	@Override
	public void remove(final String... key) {
		if (key != null) {
			for (final String val : key) {
				this.um.remove(val);
				if (this.pm != null) {
					this.pm.remove(val);
				}
			}
		}
	}

	/**
	 * キーの集合を返す。
	 *
	 * @return キー集合
	 */
	@Override
	public Set<String> keySet() {
		return this.um.keySet();
	}

	/**
	 * 内容を消去する。
	 *
	 */
	@Override
	public void clear() {
		this.um.clear();
		if (this.pm != null) {
			this.pm.clear();
		}
	}

	/**
	 * 保持している値の数を返す。
	 *
	 * @return 保持数
	 */
	@Override
	public int size() {
		return this.um.size();
	}

	/**
	 * 全ての値とキーを組にした文字列を返す。
	 *
	 * @return 文字列
	 */
	@Override
	public String toString() {
		return this.um.toString();
	}

	/**
	 * キーに対応した値が配列かを返す。
	 *
	 * @param key キー
	 * @return 配列なら true
	 */
	public boolean isArrayValue(final String key) {
		return this.um.isArrayValue(key);
	}

	/**
	 * 他のモデルの内容を全て追加する。
	 *
	 * @param model モデル
	 */
	@Override
	public void addAll(final UniModel model) {
		if (model != null) {
			for (final String key : model.keySet()) {
				Class<?> cls = model.getValueClass(key);
				if (cls == null) {
					noValue(key);
				} else {
					Serializable val = getValue(key, model);
					if (!cls.isArray() && val != null && val.getClass().isArray()) {
						val = Serializable.class.cast(Array.get(val, 0));
					}
					addValue(key, val, cls);
				}
			}
		}
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Boolean value) {
		addValue(key, value, Boolean.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Boolean... value) {
		addValue(key, value, Boolean[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final String value) {
		addValue(key, value, String.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final String... value) {
		addValue(key, value, String[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Byte value) {
		addValue(key, value, Byte.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Byte... value) {
		checkNumberArray(value);
		addValue(key, value, Byte[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Short value) {
		addValue(key, value, Short.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Short... value) {
		checkNumberArray(value);
		addValue(key, value, Short[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Integer value) {
		addValue(key, value, Integer.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Integer... value) {
		checkNumberArray(value);
		addValue(key, value, Integer[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Long value) {
		addValue(key, value, Long.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Long... value) {
		checkNumberArray(value);
		addValue(key, value, Long[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Float value) {
		addValue(key, value, Float.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Float... value) {
		checkNumberArray(value);
		addValue(key, value, Float[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Double value) {
		addValue(key, value, Double.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Double... value) {
		checkNumberArray(value);
		addValue(key, value, Double[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final BigInteger value) {
		addValue(key, value, BigInteger.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final BigInteger... value) {
		checkNumberArray(value);
		addValue(key, value, BigInteger[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final BigDecimal value) {
		addValue(key, value, BigDecimal.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final BigDecimal... value) {
		checkNumberArray(value);
		addValue(key, value, BigDecimal[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Number value) {
		if (value == null) {
			addValue(key, null, BigDecimal.class);
		} else {
			addValue(key, value, value.getClass());
		}
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Number... value) {
		if (value == null) {
			addValue(key, null, BigDecimal[].class);
		} else {
			checkNumberArray(value);
			addValue(key, value, value.getClass());
		}
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Date value) {
		addValue(key, value, Date.class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param value 値
	 */
	@Override
	public void addValue(final String key, final Date... value) {
		addValue(key, value, Date[].class);
	}

	/**
	 * キーに対応した値を追加する。
	 *
	 * @param key キー
	 * @param val 値
	 * @param cls クラス
	 */
	private void addValue(final String key, final Serializable val, final Class<?> cls) {
		// 値が存在しない場合
		if (!hasValue(key)) {
			if (cls.isArray() && val == null) {
				this.um.put(key, null, cls.getComponentType());
				this.um.toArrayValue(key);
			} else {
				this.um.put(key, val, cls);
			}
			return;
		}

		// 追加項目数取得
		int length = this.um.getArraySize(key);
		int addlen = getLength(val);
		Object[] vals = Arrays.copyOf(getObjectArray(key), length + addlen);

		if (val != null) {
			if (val.getClass().isArray()) {
				System.arraycopy(val, 0, vals, length, addlen);
			} else {
				vals[length] = val;
			}
		}
		// 保持
		this.um.put(key, vals);
	}

	/**
	 * キーに対応した値配列を取得する。
	 *
	 * @param key キー
	 * @return 値配列
	 */
	private Object[] getObjectArray(final String key) {
		Object obj = this.um.getRaw(key);
		if (this.um.isArrayValue(key)) {
			obj = this.um.get(key);
		}
		return Object[].class.cast(obj);
	}

	/**
	 * オブジェクトの項目数を取得する。
	 *
	 * @param obj 対象オブジェクト
	 * @return 項目数
	 */
	private int getLength(final Object obj) {
		if (obj != null && obj.getClass().isArray()) {
			return Array.getLength(obj);
		}
		return 1;
	}
}
